package com.i2works.smartluolong.api.config;


import com.google.common.collect.Maps;
import com.i2works.smartluolong.api.shiro.SmartWebAPIRealm;
import com.i2works.smartluolong.utils.config.ShiroConfigurationUtils;
import com.i2works.smartluolong.utils.shiro.filter.StatelessAuthcFilter;
import com.i2works.smartluolong.utils.shiro.mgt.StatelessDefaultSubjectFactory;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.Map;


/**
 * shiro配置类（采用ehcache缓存）
 *
 * @author Liu Yutao <koal@vip.qq.com>
 * @date 2017/12/18 14:05
 * <p>
 * <p>
 * 1.LifecycleBeanPostProcessor，这是个DestructionAwareBeanPostProcessor的子类，负责org.apache.shiro.util.Initializable类型bean的生命周期的，初始化和销毁。主要是AuthorizingRealm类的子类，以及EhCacheManager类。
 * 2.HashedCredentialsMatcher，这个类是为了对密码进行编码的，防止密码在数据库里明码保存，当然在登陆认证的生活，这个类也负责对form里输入的密码进行编码。
 * 3.ShiroRealm，这是个自定义的认证类，继承自AuthorizingRealm，负责用户的认证和权限的处理，可以参考JdbcRealm的实现。
 * 4.EhCacheManager，缓存管理，用户登陆成功后，把用户信息和权限信息缓存起来，然后每次用户请求时，放入用户的session中，如果不设置这个bean，每个请求都会查询一次数据库。
 * 5.SecurityManager，权限管理，这个类组合了登陆，登出，权限，session的处理，是个比较重要的类。
 * 6.ShiroFilterFactoryBean，是个factorybean，为了生成ShiroFilter。它主要保持了三项数据，securityManager，filters，filterChainDefinitionManager。
 * 7.DefaultAdvisorAutoProxyCreator，Spring的一个bean，由Advisor决定对哪些类的方法进行AOP代理。
 * 8.AuthorizationAttributeSourceAdvisor，shiro里实现的Advisor类，内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
 */

@Configuration
public class ShiroEhcacheConfiguration extends ShiroConfigurationUtils {

    //日志
    private static final Logger logger = LoggerFactory.getLogger(ShiroEhcacheConfiguration.class);

    //filterchain配置文件（可以不配置）
    @Value("${shiro.filterchain.url}")
    private String shiroFilterchainUrl;


    /**
     * 生命周期
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        logger.info("ShiroEhcacheConfiguration ----> 生命周期：getLifecycleBeanPostProcessor");
        return new LifecycleBeanPostProcessor();
    }


    /**
     * 获取配置的Realm，之所以没使用注解配置，是因为此处需要考虑到加密处理
     */
    @Bean
    public SmartWebAPIRealm smartWebRealm() {
        logger.info("ShiroEhcacheConfiguration ----> SmartWebAPIRealm");
        SmartWebAPIRealm realm = new SmartWebAPIRealm();
        realm.setCachingEnabled(false);
        return realm;
    }

    /**
     * 无状态Subject工厂
     */
    @Bean("subjectFactory")
    public StatelessDefaultSubjectFactory StatelessDefaultSubjectFactory() {
        logger.info("ShiroEhcacheConfiguration ----> 无状态Subject工厂：StatelessDefaultSubjectFactory");
        return new StatelessDefaultSubjectFactory();
    }

    /**
     * 会话管理器
     */
    @Bean(name = "sessionManager")
    public DefaultSessionManager getSessionManager() {
        logger.info("ShiroEhcacheConfiguration ----> 会话管理器：getSessionManager");
        DefaultSessionManager sessionManager = new DefaultSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        return sessionManager;
    }


    /**
     * 安全管理器
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(SmartWebAPIRealm smartWebAPIRealm, StatelessDefaultSubjectFactory subjectFactory, DefaultSessionManager sessionManager) {
        logger.info("ShiroEhcacheConfiguration ----> 安全管理器：securityManager");
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(smartWebAPIRealm);
        securityManager.setSubjectFactory(subjectFactory);
        securityManager.setSessionManager(sessionManager);
//        securityManager.setSubjectDAO(new DefaultSubjectDAO().getSessionStorageEvaluator().isSessionStorageEnabled(false));
        return securityManager;
    }

    /**
     * 相当于调用SecurityUtils.setSecurityManager(securityManager)
     */
    @Bean
    public MethodInvokingFactoryBean MethodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
        MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
        factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        factoryBean.setArguments(securityManager);
        return factoryBean;
    }


    /**
     * 创建ShiroFilter
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        logger.info("ShiroEhcacheConfiguration ----> 创建ShiroFilter：getShiroFilterFactoryBean");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 拦截器链
        Map<String, Filter> filters = Maps.newLinkedHashMap();
        //无状态拦截器
        filters.put("statelessAuthc", getStatelessAuthcFilter());
        shiroFilterFactoryBean.setFilters(filters);
        //加载权限规则
        loadShiroFilterChainRule(shiroFilterFactoryBean);
        return shiroFilterFactoryBean;
    }

    /**
     * 加载shiroFilter权限控制规则
     * <p>
     * 常用的包含anon、authc、perms、roles、user、logout。
     * 角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
     * authc绑定表单验证，如果表单拦截器传递参数不对，就会导致第一次登录无反应，第二次才能登陆成功的情况
     */
    private void loadShiroFilterChainRule(ShiroFilterFactoryBean shiroFilterFactoryBean) {
        shiroFilterFactoryBean.setFilterChainDefinitionMap(getShiroFilterChain(shiroFilterchainUrl));
    }


    /**
     * 无状态拦截器
     */
    private StatelessAuthcFilter getStatelessAuthcFilter() {
        return new StatelessAuthcFilter();
    }


}
